/*
 * @(#)Hexagon.java	1.0 Apr 27, 2008
 *
 *	The MIT License
 *
 *	Copyright (c) 2008 Malachi de AElfweald <malachid@gmail.com>
 *
 *	Permission is hereby granted, free of charge, to any person obtaining a copy
 *	of this software and associated documentation files (the "Software"), to deal
 *	in the Software without restriction, including without limitation the rights
 *	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *	copies of the Software, and to permit persons to whom the Software is
 *	furnished to do so, subject to the following conditions:
 *
 *	The above copyright notice and this permission notice shall be included in
 *	all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *	THE SOFTWARE.
 */
package org.eoti.awt.geom;

import org.eoti.awt.GraphicsUtil;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.*;
import java.util.*;
import java.io.File;

public class Hexagon
extends Polygon
{
	public enum Corner
	{
		Top, UpperRight, LowerRight, Bottom, LowerLeft, UpperLeft
	}

	public enum Direction
	{
		NorthEast, East, SouthEast, SouthWest, West, NorthWest
	}

	public enum BoundingCorner
	{
		TopLeft, TopRight, BottomLeft, BottomRight
	}

	protected Point2D center;
	protected int size;
	protected double height, width;
	protected double hOffset, wOffset;
	protected Rectangle2D boundingBox;
	protected HashMap<Corner,Point2D> corners;
	protected HashMap<BoundingCorner,Point2D> boundingCorners;

	public Hexagon(double centerX, double centerY, int size)
	{
		this(new Point2D.Double(centerX,centerY), size);
	}

	public Hexagon(Point2D center, int size)
	{
		super();
		this.center = center;
		this.size = size;

		/**
	     * MATH:
	     *	With the hexagon points={TOP, UPPER-RIGHT, LOWER-RIGHT, BOTTOM, LOWER-LEFT, UPPER-RIGHT}
	     *	size = length of each actual segment of the hexagon
	     *	width = bounding rectangle width
	     *	height = bounding rectangle height
	     *	each inner angle is 120 degrees
	     *	outside angles are 30-60-90 triangles with 30 near TOP and BOTTOM and 60 near sides
	     *	hOffset = height difference between 'size' edge and bounding rectangle corners
	     *	wOffset = width difference between TOP/BOTTOM points and bounding rectangle corners
	     */

		double thirtyDegrees = Math.toRadians(30);
		hOffset = Math.sin(thirtyDegrees) * size;
		wOffset = Math.cos(thirtyDegrees) * size;

		height = (2 * hOffset) + size;
		width = (2 * wOffset);

		double left = center.getX() - (width/2);
		double right = center.getX() + (width/2);
		double top = center.getY() - (height/2);
		double bottom = center.getY() + (height/2);
		boundingBox = new Rectangle2D.Double(left, top, width, height);

		boundingCorners = new HashMap<BoundingCorner, Point2D>();
		boundingCorners.put(BoundingCorner.TopRight, new Point2D.Double(right, top));
		boundingCorners.put(BoundingCorner.TopLeft, new Point2D.Double(left, top));
		boundingCorners.put(BoundingCorner.BottomRight, new Point2D.Double(right, bottom));
		boundingCorners.put(BoundingCorner.BottomLeft, new Point2D.Double(left, bottom));

		corners = new HashMap<Corner,Point2D>();
		corners.put(Corner.Top, new Point2D.Double(center.getX(), top));
		corners.put(Corner.UpperRight, new Point2D.Double(right, (top + hOffset)));
		corners.put(Corner.LowerRight, new Point2D.Double(right, (bottom - hOffset)));
		corners.put(Corner.Bottom, new Point2D.Double(center.getX(), bottom));
		corners.put(Corner.LowerLeft, new Point2D.Double(left, (bottom - hOffset)));
		corners.put(Corner.UpperLeft, new Point2D.Double(left, (top + hOffset)));

		for(Corner corner : Corner.values())
		{
			Point2D p2d = corners.get(corner);
			addPoint((int)p2d.getX(), (int)p2d.getY());
		}
	}

	@Override
	public String toString()
	{
		StringBuilder sb = new StringBuilder();
		sb.append("[Hexagon: ");
		sb.append(String.format(
				"[Size: %d]",
				size
			));
		sb.append(String.format(
				"[Height: %2.1f, hOffset: %2.1f]",
				height, hOffset
			));
		sb.append(String.format(
				"[Width: %2.1f, wOffset: %2.1f]",
				width, wOffset
			));
		sb.append(String.format(
				"[Center: %2.1fx%2.1f]",
				center.getX(), center.getY()
			));

		sb.append("[Corners: ");
		for(Corner corner : Corner.values())
		{
			Point2D p2d = corners.get(corner);
			sb.append(String.format(
					"[%s: %2.1fx%2.1f]",
					corner, p2d.getX(), p2d.getY()
				));
		}
		sb.append("]");

		sb.append("[Bounds: ");
		for(BoundingCorner corner : BoundingCorner.values())
		{
			Point2D p2d = boundingCorners.get(corner);
			sb.append(String.format(
					"[%s: %2.1fx%2.1f]",
					corner, p2d.getX(), p2d.getY()
				));
		}
		sb.append("]");


		sb.append(String.format(
				"[BoundingBox: %2.1fx%2.1f to %2.1fx%2.1f]",
				boundingBox.getMinX(),
				boundingBox.getMinY(),
				boundingBox.getMaxX(),
				boundingBox.getMaxY()
			));

		sb.append("]");
		return sb.toString();
	}

	public Point2D getCenter(){return center;}
	public int getSize(){return size;}
	public double getHeight(){return height;}
	public double getWidth(){return width;}

	@Override
	public Rectangle2D getBounds2D(){return boundingBox;}

	public double getHeightOffset(){return hOffset;}
	public double getWidthOffset(){return wOffset;}

	//public HashMap<Corner,Point2D> getCorners(){return corners;}
	//public HashMap<BoundingCorner,Point2D> getBoundingCorners(){return boundingCorners;}

	public Hexagon resize(int newSize)
	{
		return new Hexagon(center, newSize);
	}
	
	@Override
	public Object clone()
	{
		return new Hexagon(center, size);
	}
	public Point2D getHexPoint(Corner corner)
	{
		return corners.get(corner);
	}
	public Point2D getBoundPoint(BoundingCorner corner)
	{
		return boundingCorners.get(corner);
	}

	public void translate(double deltaX, double deltaY)
	{
		translate((int)deltaX, (int)deltaY);
	}

	@Override
	public void translate(int deltaX, int deltaY)
	{
		super.translate(deltaX, deltaY);
		boundingBox = super.getBounds2D();

		double top = boundingBox.getMinY();
		double left = boundingBox.getMinX();
		double bottom = boundingBox.getMaxY();
		double right = boundingBox.getMaxX();

		double centerX = boundingBox.getCenterX();
		double centerY = boundingBox.getCenterY();
		center = new Point2D.Double(centerX, centerY);

		boundingCorners.put(BoundingCorner.TopRight, new Point2D.Double(right, top));
		boundingCorners.put(BoundingCorner.TopLeft, new Point2D.Double(left, top));
		boundingCorners.put(BoundingCorner.BottomRight, new Point2D.Double(right, bottom));
		boundingCorners.put(BoundingCorner.BottomLeft, new Point2D.Double(left, bottom));

		corners.put(Corner.Top, new Point2D.Double(center.getX(), top));
		corners.put(Corner.UpperRight, new Point2D.Double(right, (top + hOffset)));
		corners.put(Corner.LowerRight, new Point2D.Double(right, (bottom - hOffset)));
		corners.put(Corner.Bottom, new Point2D.Double(center.getX(), bottom));
		corners.put(Corner.LowerLeft, new Point2D.Double(left, (bottom - hOffset)));
		corners.put(Corner.UpperLeft, new Point2D.Double(left, (top + hOffset)));
	}

	public void align(Rectangle2D bounds, Direction direction)
	{
		// these are defined here INSTEAD of in the switch, or it won't compile
		Point2D newTopRight, newTopLeft, newBottomRight, newBottomLeft;
		Point2D oldTopRight, oldTopLeft, oldBottomRight, oldBottomLeft;

		switch(direction)
		{
			case NorthEast:
				newTopRight = new Point2D.Double(bounds.getMaxX(), bounds.getMinY());
				oldTopRight = boundingCorners.get(BoundingCorner.TopRight);
				translate(
					newTopRight.getX() - oldTopRight.getX(),			// deltaX
					newTopRight.getY() - oldTopRight.getY()				// deltaY
				);
				break;
			case East:
				newTopRight = new Point2D.Double(bounds.getMaxX(), bounds.getMinY());
				oldTopRight = boundingCorners.get(BoundingCorner.TopRight);
				translate(
					newTopRight.getX() - oldTopRight.getX(),			// deltaX
					0													// deltaY
				);
				break;
			case SouthEast:
				newBottomRight = new Point2D.Double(bounds.getMaxX(), bounds.getMaxY());
				oldBottomRight = boundingCorners.get(BoundingCorner.BottomRight);
				translate(
					newBottomRight.getX() - oldBottomRight.getX(),		// deltaX
					newBottomRight.getY() - oldBottomRight.getY()		// deltaY
				);
				break;
			case SouthWest:
				newBottomLeft = new Point2D.Double(bounds.getMinX(), bounds.getMaxY());
				oldBottomLeft = boundingCorners.get(BoundingCorner.BottomLeft);
				translate(
					newBottomLeft.getX() - oldBottomLeft.getX(),		// deltaX
					newBottomLeft.getY() - oldBottomLeft.getY()			// deltaY
				);
				break;
			case West:
				newTopLeft = new Point2D.Double(bounds.getMinX(), bounds.getMinY());
				oldTopLeft = boundingCorners.get(BoundingCorner.TopLeft);
				translate(
					newTopLeft.getX() - oldTopLeft.getX(),				// deltaX
					0													// deltaY
				);
				break;
			case NorthWest:
				newTopLeft = new Point2D.Double(bounds.getMinX(), bounds.getMinY());
				oldTopLeft = boundingCorners.get(BoundingCorner.TopLeft);
				translate(
					newTopLeft.getX() - oldTopLeft.getX(),				// deltaX
					newTopLeft.getY() - oldTopLeft.getY()				// deltaY
				);
				break;
		}
	}

	public void attach(Hexagon toTranslate, Direction direction)
	{
		double horSize = size + hOffset;
		/**
	     * To start with, we need to know toTranslate's position RELATIVE to ours
	     */
	    Point2D topLeft = getBoundPoint(BoundingCorner.TopLeft);
		Point2D toTranslateTopLeft = toTranslate.getBoundPoint(BoundingCorner.TopLeft);
	    double deltaX = topLeft.getX() - toTranslateTopLeft.getX();
	    double deltaY = topLeft.getY() - toTranslateTopLeft.getY();
	    // that should be enough to line them up exactly... now offset it...

		switch(direction)
		{
			case NorthEast:
				deltaX += wOffset;
				deltaY -= horSize;
				break;
			case East:
				deltaX += width;
				break;
			case SouthEast:
				deltaX += wOffset;
				deltaY += horSize;
				break;
			case SouthWest:
				deltaX -= wOffset;
				deltaY += horSize;
				break;
			case West:
				deltaX -= width;
				break;
			case NorthWest:
				deltaX -= wOffset;
				deltaY -= horSize;
				break;
		}
		toTranslate.translate(deltaX, deltaY);
	}

	public static class CreateTile
	{
		public static void main(String[] args)
		{
			if(args.length != 2)
			{
				System.out.println("USAGE: java org.eoti.awt.geom.Hexagon$CreateTile size filename");
				System.out.print("Output file can be: ");
				for(String name : ImageIO.getWriterFormatNames())
					System.out.print(name + ",");

				System.out.println();
				System.exit(0);
			}

			try{
				int size = Integer.parseInt(args[0]);
		    	Hexagon hex = new Hexagon(0, 0, size);
		    	Rectangle2D newBounds = new Rectangle2D.Double(0, 0, hex.getWidth(), hex.getHeight());
		    	hex.align(newBounds, Direction.NorthWest);

		    	File file = GraphicsUtil.write(args[1], hex);
		    	if(file == null)
		    		System.out.println("Error creating file");
		    	else
			    	System.out.println("Hexagon tile created: " + file.getAbsolutePath());
		    }catch(Exception e){
		    	System.out.format("Exception: %s", e.getMessage());
		    	e.printStackTrace();
		    }
		}
	}
}
